Skip to main content

유니온 파인드

유니온 파인드(Union Find)

  • 그래프 알고리즘의 종류로 '합집합 찾기'라는 의미를 가지고 있다.
  • 상호 배타적 집합(Disjoint set)이라고도 불린다.
  • 노드의 루트 노드를 찾는 Find 과정과 노드를 합치는 Union 과정, 총 2가지 연산으로 이루어져 있다.
  • 두 노드가 같은 그래프에 속하는지 판별하는 알고리즘이다.
  • 트리 구조로 이루어져 있다.
  • 구현이 간단하고 동작 속도가 매우 빠르기 때문에 다른 알고리즘의 일부로 자주 사용된다.

그래프의 연결성을 확인하는 크루스칼 알고리즘이나 가장 큰 집합을 추적하는데 Union-Find를 적용할 수 있다.

파인드 과정(Find)

원하는 노드의 루트 노드를 찾는 과정이다.

<잘못된 예시>

int find(int x){
if (x == parent[x]){
return x;
}
else {
return find(parent[x]);
}
}

자신이 루트 노드이면 자기 자신을 return 한다.

그렇지 않다면 자신 부모가 루트 노드인지 확인한다.

하지만 위의 코드의 경우 문제점이 발생한다.

Find과정_1

위 그림과 같이 한쪽으로 치우쳐진 tree의 경우 루트 노드를 찾는데 O(N)의 시간복잡도가 걸리게 된다.

int find(int x){
if (x == parent[x]){
return x;
}
else{
return parent[x]=find(parent[x]);
}
}

위 코드대로 하게 되면 루트노드를 찾게 되면 x의 부모를 바로 루트노드로 바꾸어 주어 아래의 그림과 같이 바꿔지게 됩니다. 이로써 find의 효율이 높아지게 된다.

Find과정_2

Find 함수의 시간 복잡도는 트리 형태의 그래프이기 때문에 O(logN)이 된다.

배열로 표현한 경우에는 O(N)만큼 걸리기 때문에 트리 형태의 그래프로 하는것이 효율적이다.

유니온 과정(Union)

매개변수로 두 개의 값을 받아서 두 노드가 포함되어 있는 각각의 집합을 합쳐줘야한다.

하나의 예시로 y의 집합을 x의 집합에 합쳐보자.

<잘못된 예시>

void union(int x,int y){
x = find(x);
y = find(y);
if (x!=y)
parent[y] = x;
}

find 함수를 사용하여 각각의 root를 찾고, 두 집합의 루트가 다른 경우 y의 부모노드를 x로 바꿔주었다.

하지만 높이가 높은 트리가 높이가 낮은 트리 밑으로 들어가게 되면 트리가 더 깊어지게 된다. 따라서 트리의 높이가 낮은 트리가 트리의 높이가 높은 트리 밑으로 들어가야 한다.

이를 위해 트리의 높이를 기록해 두는 rank 배열을 추가한다.

<union-by-rank 기법>

void union(int x, int y){
x = find(x);
y = find(y);

if (x == y)
return;

if (rank[x] > rank[y]){
parent[y] = x;
rank[x] += rank[y];
}
else {
parent[x] = y;
rank[y] += rank[x];
}
}

find 함수로 각각의 루트 노드를 찾고난 후 만약 루트 노드가 같다면 return 해주고

루트노드가 다르다면 높이가 높은 트리에 높이가 낮은 트리를 붙여준다.

Find 함수의 시간 복잡도는 트리 형태의 그래프이기 때문에 O(logN)이 된다.

무게를 고려한 유니온 과정(Weighted Union)

위의 경우에는 parent 배열과 size 배열이 존재해서 메모리를 두 배로 사용하게 된다. 이를 개선하고자 나온 방법이 바로 Weighted Union Find 방법이다.

parent 배열은 음수일 경우에 부모노드로서 음수의 절대값이 size 되고, 양수일 경우에는 부모노드를 가리키게 된다.

예를 들어, parent[2] = -2 일 경우에 2번 노드 밑에 1개의 노드가 더 있어서 총 2개의 노드가 존재한다는 의미이고, parent[4] = 6 일 경우에는 4번 노드의 부모가 6번 노드라는 의미이다.

int parent[MAX_SIZE];

for (int i=0; i<MAX_SIZE; i++)
parent[i] = -1;

int find(int x){
if (parent[x] < 0){
return x;
}
else{
int y = find(parent[x]);
parent[x] = y;
return y;
}
}

void union(int x, int y){
x = find(x);
y = find(y);

if (x == y)
return;

// parent[x], parent[y] 값은 음수이므로 값이 작은 경우가 더 높이가 큰 노드이다.
if (parent[x] < parent[y]){
parent[x] += parent[y];
parent[y] = x;
}
else {
parent[y] += parent[x];
parent[x] = y;
}
}

나올 수 있는 면접 질문

  • 유니온 파인드 알고리즘이란?

  • Weighted Union을 사용하는 이유는?

참고 url

apexcel - weighted union

begin_fill - union find 이미지

crocus - 시간 복잡도

ssung.k - union find 알고리즘이란

기여자


HelloNaks

📦